<?php
###########################################################################
#  Copyright (C) 2006-2007 Glyphix, Inc. <briz@glyphix.com>               #
#                                                                         #
#  Permission is hereby granted, free of charge, to any person obtaining  #
#  a copy of this software and associated documentation files (the        #
#  "Software"), to deal in the Software without restriction, including    #
#  without limitation the rights to use, copy, modify, merge, publish,    #
#  distribute, sublicense, and/or sell copies of the Software, and to     #
#  permit persons to whom the Software is furnished to do so, subject to  #
#  the following conditions:                                              #
#                                                                         #
#  The above copyright notice and this permission notice shall be         #
#  included in all copies or substantial portions of the Software.        #
#                                                                         #
#  Except as contained in this notice, the name(s) of the above           #
#  copyright holders shall not be used in advertising or otherwise to     #
#  promote the sale, use or other dealings in this Software without       #
#  prior written authorization.                                           #
#                                                                         #
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        #
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     #
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
#  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR      #
#  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  #
#  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
#  OTHER DEALINGS IN THE SOFTWARE.                                        #
###########################################################################

/**
 * GXGoogleAuth
 *
 * Authenticates against a Google account.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	Modules
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 * @uses		GXModule, curl
 */

class GXGoogleAuth extends GXModule {

	// curl user agent string
	private $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax)';

	// google auth params
	private $domain;		// the domain all provided email addresses must be part of
	private $url;			// the google auth url (ClientLogin)
	private $type;			// the type of google auth to use (GOOGLE, HOSTED or HOSTED_OR_GOOGLE)
	private $timeout = 15;	// the number of minutes the session will time out without any activity
	private $token;			// the token provided by google

	// the member's cookie name
	private $cookieName = 'adminid';
	private $authCookieName = 'gtoken';
	// this member's id ... the user's email address
	private $adminid;

	// init GXModule and process conf
	public function __construct( $config = null ){
		parent::__construct();
		$this->GXCookie = GXCookie::getInstance();

		$this->domain = $config->exec->xpath('domain',0)->nodeValue;
		$this->type = $config->exec->xpath('type',0)->nodeValue;
		$this->url = $config->exec->xpath('url',0)->nodeValue;
		$this->timeout = $config->exec->xpath('timeout',0)->nodeValue;

		// if no session, check the cookie
		if( !$this->adminid = $this->getUserID() ){
			trigger_error( 'Admin User is not authenticated', E_USER_WARNING );
			$this->GXCookie->delete( $this->cookieName );
			unset($this->adminid);
		}else{
			// freshen session
			$this->setSession($this->adminid);
		}

		return true;
	}

	/**
	 * getUserID
	 * Pulls the admin username from the cookie.
	 * If the user is not logged, the ID is pulled from GXPage_Params.
	 * @return string username or false on failure
	 */
	public function getUserID(){
		if( !isset($this->adminid) || !$this->adminid ){
			$this->adminid = $this->GXPage->Params->{$this->cookieName};
		}
		return $this->adminid;
	}

	/*
	 * showUser
	 * Builds XML for the current user.
	 * @return object GXDOM
	 */
	public function showUser(){
		if( !$this->adminid ){
			trigger_error( 'Administrator is not logged in', E_USER_ERROR );
			return false;
		}
		$user = new GXDOM('User');
		$user->FirstName( $this->adminid );
		$user->LastName();
		$auth = $user->Auth();
		$auth->Role(null,array('ID'=>'MEMBER'));
		$auth->Role(null,array('ID'=>'ADMIN'));
		return $user;
	}

	/**
	 * login
	 *
	 * Authenticates a user by calling Google's ClientLogin API.
	 * @param string $username the user's full email address
	 * @param string $password the user's password
	 * @return string the Auth token or false on failure
	 */
	public function login( $username = null, $password = null ){
		if( !$username || !$password ){
			trigger_error( 'Missing username or password', E_USER_ERROR );
			return false;
		}
		// username must be a valid email address
		if( !eregi('^[a-z0-9\._-]+' .'@' .'([a-z0-9][a-z0-9-]*[a-z0-9]\.)+' .'([a-z]+\.)?' .'([a-z]+)$',$username) ){
			trigger_error( 'That email address does not appear to be valid (' .$username .'). Use your entire Google Account email address.', E_USER_NOTICE );
			return false;
		}
		// username must be @domain
		if( strpos($username,$this->domain) < 1 ){
			trigger_error( 'That email address is not part of an authorized domain.', E_USER_NOTICE );
			return false;
		}

		// clean the username
		$username = trim($username);
		// set up the post args
		$args = array(
			'accountType'	=> $this->type,
			'Email'			=> $username,
			'Passwd'		=> $password,
			'service'		=> 'xapi',
			'source'		=> 'remindermed.com'
			);

		// instantiate curl
		$ch = curl_init();
		// set the options
		curl_setopt( $ch, CURLOPT_URL, 				$this->url );
		curl_setopt( $ch, CURLOPT_HEADER,			true );
		curl_setopt( $ch, CURLOPT_POST, 			true );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, 		http_build_query($args) );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 	true );
		curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER,	false );
		curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST,	false );
		curl_setopt( $ch, CURLOPT_HTTPHEADER,		array('Content-type: application/x-www-form-urlencoded') );
		// run the REST
		$r = curl_exec($ch);
		$curlError = curl_errno($ch);

		// check for errors
		if( curl_errno($ch) ){
			trigger_error( 'curl err for ' .$this->url .': ' .curl_errno($ch) .'::' .curl_error($ch), E_USER_ERROR );
			curl_close($ch);
			return false;
		}

		// get the http code
		$code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );

		// parse the response
		$pairs = array();
        $lines = preg_split('/(?:\r\n|\n|\r)/', $r);
		$matches = array();
        foreach( $lines as $line ){
            if( preg_match('/^\s*([^\s=]+)\s*=\s*(.+)$/', $line, $matches) ){
                $pairs[ strtolower($matches[1]) ] = trim($matches[2]);
            }
        }

		// if we don't have a 200 response, it's an error
		if( $code != '200' ){
			// spit out user-friendly errors
			$msg = null;
			switch( $pairs['error'] ){
				case 'BadAuthentication':
					$msg = 'The login request used a username or password that is not recognized.';
					break;
				case 'NotVerified':
					$msg = 'The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.';
					break;
				case 'TermsNotAgreed':
					$msg = 'The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.';
					break;
				case 'CaptchaRequired':
					$msg = 'A CAPTCHA is required. remindermed.com does not handle this yet.';
					break;
				case 'Unknown':
					$msg = 'The error is unknown or unspecified; the request contained invalid input or was malformed.';
					break;
				case 'AccountDeleted':
					$msg = 'The user account has been deleted.';
					break;
				case 'AccountDisabled':
					$msg = 'The user account has been disabled.';
					break;
				case 'ServiceDisabled':
					$msg = 'The user\'s access to the specified service has been disabled. (The user account may still be valid.)';
					break;
				case 'ServiceUnavailable':
					$msg = 'The service is not available; try again later.';
					break;
				default:
					$msg = 'Some weirdo strange error occurred: ' .print_r($pairs,true);
			}
			trigger_error( $msg, E_USER_NOTICE );
			return false;
		}

		// set the user's info
		$this->adminid = $username;
		$this->setSession($username,$pairs['Auth']);
		return $username;
	}

	/**
	 * logout
	 *
	 * Logout function. Duh.
	 * @return boolean
	 */
	public function logout(){
		$this->GXCookie->delete( $this->cookieName );
		$this->GXCookie->delete( $this->authCookieName );
		trigger_error( 'You have successfully signed out.', E_USER_NOTICE );
		return true;
	}

	/**
	 * setSession
	 *
	 * Sets the user's session cookie.
	 * Call each time the page is accessed in order to refresh the user's session.
	 * @return boolean
	 */
	public function setSession( $adminid = null, $auth = null ){
		if( !$adminid ){
			trigger_error( 'Missing adminid', E_USER_ERROR );
			return false;
		}
		// calc timeout ... convert to seconds
		$timeout = ($this->timeout) ? $this->timeout * 60 : null;
		$this->GXCookie->set( $this->cookieName, $adminid, $timeout );
		// set a cookie to track the auth token
		$token = ($auth) ? $auth : $this->GXCookie->get($this->authCookieName);
		$this->GXCookie->set( $this->authCookieName, $token, $timeout );
		return true;
	}

}

?>
